package com.anjiplus.template.gaea.business.modules.rules.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.anjiplus.template.gaea.business.code.ResponseCode;
import com.anji.plus.gaea.bean.ResponseBean;
import com.anji.plus.gaea.constant.Enabled;
import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper;
import com.anji.plus.gaea.exception.BusinessExceptionBuilder;
import com.anji.plus.gaea.i18.MessageSourceHolder;
import com.anjiplus.template.gaea.business.modules.rules.controller.dto.GaeaRulesFieldsDTO;
import com.anjiplus.template.gaea.business.modules.rules.dao.GaeaRulesHistoryMapper;
import com.anjiplus.template.gaea.business.modules.rules.dao.GaeaRulesMapper;
import com.anjiplus.template.gaea.business.modules.rules.dao.entity.GaeaRules;
import com.anjiplus.template.gaea.business.modules.rules.dao.entity.GaeaRulesHistory;
import com.anjiplus.template.gaea.business.modules.rules.service.GaeaRulesService;
import com.anjiplus.template.gaea.common.util.CustomStringJavaCompiler;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.Expression;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import static com.anji.plus.gaea.bean.ResponseBean.builder;

/**
 * (GaeaRules)ServiceImpl
 *
 * @author lirui
 * @since 2021-03-12 10:38:19
 */
@Service
public class GaeaRulesServiceImpl implements GaeaRulesService {
    Logger logger = LoggerFactory.getLogger(GaeaRulesServiceImpl.class);

    @Autowired
    private GaeaRulesMapper  gaeaRulesMapper;

    @Autowired
    private GaeaRulesHistoryMapper gaeaRulesHistoryMapper;

    @Autowired
    private MessageSourceHolder messageSourceHolder;

    @Override
    public GaeaBaseMapper<GaeaRules> getMapper() {
        return  gaeaRulesMapper;
    }

    /**
     *
     * @param ruleCode  规则编码
     * @param ruleContent 规则内容
     * @param rulesDemo 测试数据
     * @return
     */
    @Override
    public ResponseBean executeRules(String ruleCode, String ruleContent, String rulesDemo) {

        Map<String, Object> ruleDataMap = JSONObject.parseObject(rulesDemo);
        if (StringUtils.isBlank(ruleCode) || StringUtils.isBlank(ruleContent)
            || ruleDataMap == null || ruleDataMap.size() ==0) {
            throw BusinessExceptionBuilder.build(ResponseCode.PARAM_IS_NULL);
        }
        // 规则校验
        List list = checkRules(ruleCode, ruleDataMap);
        if (CollectionUtils.isNotEmpty(list)) {
            return builder().code(ResponseCode.RULE_FIELDS_CHECK_ERROR).message(list.toString()).build();
        }
        String result = null;
        if (ruleContent.indexOf("class") == -1) {
            // 表达式动态求值
            result = getExpressionValue(ruleContent, ruleDataMap);
        } else {
            // class类动态编译执行
            result = getClassCompileValue(ruleContent, ruleDataMap);
        }
        // 保存规则相关到历史记录
        GaeaRulesHistory gaeaRulesHistory = new GaeaRulesHistory();
        gaeaRulesHistory.setRuleCode(ruleCode);
        gaeaRulesHistory.setRuleContent(ruleContent);
        gaeaRulesHistory.setRuleDemo(rulesDemo);
        gaeaRulesHistory.setRuleResult(result);
        gaeaRulesHistoryMapper.insert(gaeaRulesHistory);
        return builder().data(result).build();
    }

    /**
     * class类动态编译执行
     * @param ruleContent 规则内容
     * @param ruleDataMap 测试数据Json
     * @return
     */
    private String getClassCompileValue(String ruleContent, Map<String, Object> ruleDataMap) {
        String result;
        CustomStringJavaCompiler compiler = new CustomStringJavaCompiler(ruleContent);
        boolean res = compiler.compiler();
        if (res) {
            logger.info("编译成功, compilerTakeTime：" + compiler.getCompilerTakeTime());
            try {
                compiler.runMethod(ruleDataMap);
                logger.info("runTakeTime：" + compiler.getRunTakeTime());
            } catch (Exception e) {
                logger.info("运行异常: " + e);
                throw BusinessExceptionBuilder.build(ResponseCode.RULE_CONTENT_EXECUTE_ERROR);
            }
            result = compiler.getRunResult();
            logger.info(result);
            logger.info("诊断信息：" + compiler.getCompilerMessage());
        } else {
            logger.info("编译失败: " + compiler.getCompilerMessage());
            throw BusinessExceptionBuilder.build(ResponseCode.RULE_CONTENT_EXECUTE_ERROR);
        }
        return result;
    }

    /**
     *  表达式动态求值
     * @param ruleContent 规则内容
     * @param ruleDataMap 测试数据Json
     * @return
     */
    private String getExpressionValue(String ruleContent, Map<String, Object> ruleDataMap) {
        // 数据转换
        ruleDataMap.forEach((k, v) -> {
            if (isNumeric(String.valueOf(v))) {
                ruleDataMap.put(k, new BigDecimal(String.valueOf(v)));
            }
        });
        Expression compileExp;
        try {
            compileExp = AviatorEvaluator.compile(ruleContent);
        } catch (Exception e) {
            logger.info("编译失败: " + e);
            throw BusinessExceptionBuilder.build(ResponseCode.RULE_CONTENT_COMPILE_ERROR);
        }
        String result;
        try {
            result = compileExp.execute(ruleDataMap).toString();
        } catch (Exception e) {
            logger.info("运行异常: " + e);
            throw BusinessExceptionBuilder.build(ResponseCode.RULE_CONTENT_EXECUTE_ERROR);
        }
        return result;
    }

    /**
     * 校验规则
     * @param ruleCode 规则编码
     * @param ruleDataMap 测试数据Json
     * @return
     */
    private List<String> checkRules(String ruleCode, Map<String, Object> ruleDataMap) {
        // 获取编码对应规则
        LambdaQueryWrapper<GaeaRules> queryWrapper= Wrappers.lambdaQuery();
        queryWrapper.eq(GaeaRules::getRuleCode, ruleCode)
                .eq(GaeaRules::getEnabled, Enabled.YES.getValue());
        List<GaeaRules> gaeaRules = gaeaRulesMapper.selectList(queryWrapper);
        if (CollectionUtils.isEmpty(gaeaRules)) {
            throw BusinessExceptionBuilder.build(ResponseCode.RULE_CONTENT_NOT_EXIST);
        }
        String ruleFields = gaeaRules.get(0).getRuleFields();
        if (StringUtils.isBlank(ruleFields)) {
            throw BusinessExceptionBuilder.build(ResponseCode.RULE_FIELDS_NOT_EXIST);
        }
        List<GaeaRulesFieldsDTO> gaeaRulesFields = JSONObject.parseArray(ruleFields, GaeaRulesFieldsDTO.class);
        List<String> list = new ArrayList<>();
        gaeaRulesFields.forEach(e -> {
            StringBuilder stringBuilder = new StringBuilder();
            if (e.getRequire()) {
                Object fieldValue = ruleDataMap.get(e.getName());
                if (ObjectUtils.isEmpty(fieldValue)) {
                    stringBuilder.append(e.getName()).append(": ")
                        .append(messageSourceHolder.getMessage(ResponseCode.RULE_FIELD_VALUE_IS_REQUIRED));
                } else {
                    if ((e.getType().equalsIgnoreCase("Number") && !(fieldValue instanceof Number))
                    || (e.getType().equalsIgnoreCase("Boolean") && !(fieldValue instanceof Boolean))
                    || (e.getType().equalsIgnoreCase("String") && !(fieldValue instanceof String))) {
                        if (stringBuilder.length() == 0) {
                            stringBuilder.append(e.getName()).append(": ");
                        } else {
                            stringBuilder.append("，");
                        }
                        stringBuilder.append(messageSourceHolder.getMessage(ResponseCode.RULE_FIELD_VALUE_TYPE_ERROR));
                    }
                }
                if (stringBuilder.length() != 0) {
                    list.add(stringBuilder.toString());
                }
            }
        });
        return list;
    }

    // 判断一个字符串是否为数字
    private static boolean isNumeric(String str) {
        Pattern pattern = Pattern.compile("-?[0-9]+.*[0-9]*");
        return pattern.matcher(str).matches();
    }

}
